using System;
using System.Collections.Generic;
using OPTFleet.Data;
using OPTFleet.RouteService;
using System.Text;
using System.Text.RegularExpressions;
using System.Drawing;
using OPTFleet.OPTFleet.Modules;

namespace OPTFleet.OPTFleet {
	public class Operacoes {
        private const int SUCESSO = 1;
        private const int NAO_COUBE = 2;
        private const int VEICULO_CHEIO = 3;
        private static PointF armazem = new PointF(41, -8);

        private NewRoutes newRoutes;
        private CamadaDados cd;
        private int processado;

        public Operacoes(CamadaDados cd)
        {
            this.cd = cd;
        }

        public void SetNewRoutes(NewRoutes nr)
        {
            this.newRoutes = nr; 
        }

        public static void SetTempoEntregas(Planeamento p)
        {
            string ret = "";
            long soma = 0;
            List<long> tempos = new List<long>();
            int i = 0;
            DateTime dt;

            ret += armazem.X+","+armazem.Y+";";

            foreach(Entrega e in p.Entregas)
            {
                ret += e.Encomenda.Cliente.Local.Longitude + "," + e.Encomenda.Cliente.Local.Latitude + ";";
            }
            ret += armazem.X+","+armazem.Y;
            //ret.Remove(ret.Length-1);

            tempos = CreateRoute(ret);

            foreach(Entrega e in p.ShallowEntregas)
            {
                dt = DateTime.Today;
                dt = dt.AddHours(Funcionario.HORA_DE_ENTRADA);
                if(tempos[i] < 90) tempos[i] = 90;
                soma += tempos[i];
                dt = dt.AddSeconds(soma);
                e.Hora = dt;
                //Console.WriteLine("Hora " + i + ": " + (soma/3600) + " (" + tempos[i] + ")");
                i++;
            }
            dt = DateTime.Today;
            dt = dt.AddHours(Funcionario.HORA_DE_ENTRADA);
            if(tempos[i] < 90) tempos[i] = 90;
            soma += tempos[i];
            dt = dt.AddSeconds(soma);
            p.HoraChegada = dt; 
            //Console.WriteLine("Hora final " + i + ": " + (soma+tempos[i])/3600 + " (" + tempos[i] + ")");
        }

        public static string ParsePlaneamentoToURL(Planeamento p)
        {
            string url = "http://www.bing.com/maps/default.aspx?Rtp=";

            url += "pos." + armazem.X + "_" + armazem.Y + "~";
            foreach(Entrega e in p.Entregas)
            {
                url += "pos." + e.Encomenda.Cliente.Local.Longitude.ToString() + "_" + e.Encomenda.Cliente.Local.Latitude.ToString() + "~";
            }
            url += "pos." + armazem.X + "_" + armazem.Y;
            //url = url.Remove(url.Length - 1);

            return url;
        }

        public static List<long> CreateRoute(string waypointString)
        {
            //string results = "";
            string key = "AvKgB2sBq4AKvfduNosfNU9AK5eAKnVFyW_oB8TaDf2DPesdn0QRUkVeZu_ci21m";
            RouteRequest routeRequest = new RouteRequest();

            // Set the credentials using a valid Bing Maps key
            routeRequest.Credentials = new RouteService.Credentials();
            routeRequest.Credentials.ApplicationId = key;

            //Parse user data to create array of waypoints
            string[] points = waypointString.Split(';');
            Waypoint[] waypoints = new Waypoint[points.Length];

            int pointIndex = -1;
            foreach (string point in points)
            {
                pointIndex++;
                waypoints[pointIndex] = new Waypoint();
                string[] digits = point.Split(','); waypoints[pointIndex].Location = new RouteService.Location();
                waypoints[pointIndex].Location.Latitude = double.Parse(digits[0].Trim());
                waypoints[pointIndex].Location.Longitude = double.Parse(digits[1].Trim());

                if (pointIndex == 0)
                    waypoints[pointIndex].Description = "Start";
                else if (pointIndex == points.Length)
                    waypoints[pointIndex].Description = "End";
                else
                    waypoints[pointIndex].Description = string.Format("Stop #{0}", pointIndex);
            }

            routeRequest.Waypoints = waypoints;

            // Make the calculate route request
            RouteServiceClient routeService = new RouteServiceClient("BasicHttpBinding_IRouteService");
            RouteResponse routeResponse = routeService.CalculateRoute(routeRequest);
            
            // Iterate through each itinerary item to get the route directions
            StringBuilder directions = new StringBuilder("");

            if (routeResponse.Result.Legs.Length > 0)
            {
                List<long> tempos = new List<long>();

                foreach (RouteLeg leg in routeResponse.Result.Legs)
                {
                    tempos.Add(leg.Summary.TimeInSeconds);
                }

                return tempos;
                /*
                int instructionCount = 0;
                int legCount = 0;

                foreach (RouteLeg leg in routeResponse.Result.Legs)
                {
                    legCount++;
                    directions.Append(string.Format("Leg #{0}\n", legCount));

                    foreach (ItineraryItem item in leg.Itinerary)
                    {
                        instructionCount++;
                        directions.Append(string.Format("{0}. {1}\n",
                            instructionCount, item.Text));
                    }
                }


                directions.Append("------------SUMMARY-------------\n");
                directions.Append(routeResponse.Result.Summary.Distance; + "\n");
                directions.Append(routeResponse.Result.Summary.TimeInSeconds+"\n");
                directions.Append("-----------ROUTEPATH------------\n");
                directions.Append(routeResponse.Result.RoutePath.Points+"\n");

                //Remove all Bing Maps tags around keywords.  
                //If you wanted to format the results, you could use the tags
                Regex regex = new Regex("<[/a-zA-Z:]*>",
                  RegexOptions.IgnoreCase | RegexOptions.Multiline);
                results = regex.Replace(directions.ToString(), string.Empty);
            }
            else
                results = "No Route found";        
                */
            }
            return null;
        }

		public void CalcularPlaneamento(DateTime dia)
        {
            List<Motorista> ms;
            SortedSet<Veiculo> vs;
            SortedSet<Encomenda> es;
            List<Planeamento> ps;
            Object[] data = new Object[10];

            vs = (SortedSet<Veiculo>)cd.OperacaoDados(CamadaDados.OP_GET_VEICULOS_ORD, null);
            ms = (List<Motorista>)cd.OperacaoDados(CamadaDados.OP_GET_MOTORISTAS_DISPONIVEIS,null);

            switch (dia.DayOfWeek.ToString())
            {
                case "Monday": data[0] = 1;
                            break;
                case "Tuesday": data[0] = 2;
                            break;
                case "Wednesday": data[0] = 3;
                            break;
                case "Thursday": data[0] = 4;
                            break;
                case "Friday": data[0] = 5;
                            break;
                default :
                            break;
            }
            //Console.WriteLine("Dia: " + data[0]);
            es = (SortedSet<Encomenda>)cd.OperacaoDados(CamadaDados.OP_GET_ENCOMENDAS,data);
            /*
            Console.WriteLine("No Encomendas: " + es.Count);
            
            Console.WriteLine("-------------------Encomendas---------------------");
            foreach (Encomenda e in es)
                Console.WriteLine(e);
            Console.WriteLine("-------------------Veiculos-----------------------");
            foreach (Veiculo v in vs)
                Console.WriteLine(v);
            Console.WriteLine("-------------------Motoristas----------------------");
            foreach (Motorista m in ms)
                Console.WriteLine(m);
            */
            ps = Algoritmo(ms, vs, es);

            CalcularRotas(ps);
            /*
            Console.WriteLine("--------------------PLANEAMENTOS------------------");
            for (int i = 0; i < ps.Count; i++)
            {
                Console.WriteLine("############################## PLANEAMENTO " + i);
                Console.WriteLine(ps[i].ToString());
            }
            */
            data[0] = ps;
            cd.OperacaoDados(CamadaDados.OP_SET_PLANEAMENTOS, data);
		}


        public void CalcularRotas(List<Planeamento> ps)
        {
            DateTime maxHora = DateTime.Today;
            IEnumerator<Entrega> rit;
            Entrega entrega;
            bool b;

            maxHora = maxHora.AddHours(Funcionario.HORA_DE_SAIDA);
            foreach (Planeamento p in ps)
            {
                SetTempoEntregas(p);
                while(p.HoraChegada > maxHora){
                    /*
                    rit = p.ShallowEntregas.Reverse().GetEnumerator();
                    rit.MoveNext();
                    entrega = rit.Current;
                    b = p.ShallowEntregas.Remove(entrega);
                    SetTempoEntregas(p);
                    */
                    rit = p.ShallowEntregas.GetEnumerator();
                    b = rit.MoveNext();
                    if (!b) break;
                    entrega = rit.Current;
                    p.ShallowEntregas.Remove(entrega);
                    SetTempoEntregas(p);
                }
            }
            newRoutes.setProgress(100);
        }
        /*
        private Boolean CheckSobreposicao(Ponto2D xy, Ponto2D xxy, Ponto2D xyy, Ponto2D xxyy, Double X, Double Y)
        {
            if (xy.GetX() <= X || xy.GetY() <= Y)
                return false;
            if (xxyy.GetX() >= X || xxyy.GetY() >= Y)
                return false;
            return true;
        }
        */
        private Boolean CheckSobreposicao(Ponto2D ponto, Entrega entrega,Encomenda encomenda)
        {
            //Boolean b;
            Double maxX,maxY,minX,minY;
            Ponto2D pontoX=ponto.Clone(),pontoY=ponto.Clone(),pontoXY=ponto.Clone();
            PointF[] intersetantes;

            minX = entrega.PosX;
            minY = entrega.PosY;
            maxX = entrega.PosX + entrega.Encomenda.Largura;
            maxY = entrega.PosY + entrega.Encomenda.Comprimento;

            pontoX.AddX(encomenda.Largura);
            pontoY.AddY(encomenda.Comprimento);
            pontoXY.AddX(encomenda.Largura);
            pontoXY.AddY(encomenda.Comprimento);
            /*
            if ((entrega.Encomenda.IDencomenda == 3 || entrega.Encomenda.IDencomenda == 1) && (encomenda.IDencomenda == 3 || encomenda.IDencomenda == 1))
            {
                for (int i = 0; i < 10; i++)
                    Console.WriteLine("PONTO 1 e PONTO 3!!!");
                Console.WriteLine(entrega.PosX);
                Console.WriteLine(entrega.PosY);
                Console.WriteLine(entrega.PosX + entrega.Encomenda.Largura);
                Console.WriteLine(entrega.PosY + entrega.Encomenda.Comprimento);
                Console.WriteLine(ponto.ToString());
                Console.WriteLine(pontoX.ToString());
                Console.WriteLine(pontoY.ToString());
                Console.WriteLine(pontoXY.ToString());
            }
            */
            intersetantes = Intersector.Intersection(new PointF((float)minX,(float)minY),new PointF((float)minX,(float)maxY),new PointF((float)ponto.GetX(),(float)ponto.GetY()),new PointF((float)pontoX.GetX(),(float)pontoX.GetY()));
            if (intersetantes.Length >= 1) return true;

            intersetantes = Intersector.Intersection(new PointF((float)minX, (float)minY), new PointF((float)minX, (float)maxY), new PointF((float)pontoY.GetX(), (float)pontoY.GetY()), new PointF((float)pontoXY.GetX(), (float)pontoXY.GetY()));
            if (intersetantes.Length >= 1) return true;

            intersetantes = Intersector.Intersection(new PointF((float)minX, (float)maxY), new PointF((float)maxX, (float)maxY), new PointF((float)ponto.GetX(), (float)ponto.GetY()), new PointF((float)pontoX.GetX(), (float)pontoX.GetY()));
            if (intersetantes.Length >= 1) return true;

            intersetantes = Intersector.Intersection(new PointF((float)minX, (float)maxY), new PointF((float)maxX, (float)maxY), new PointF((float)pontoY.GetX(), (float)pontoY.GetY()), new PointF((float)pontoXY.GetX(), (float)pontoXY.GetY()));
            if (intersetantes.Length >= 1) return true;

            intersetantes = Intersector.Intersection(new PointF((float)ponto.GetX(),(float)ponto.GetY()),new PointF((float)pontoY.GetX(),(float)pontoY.GetY()),new PointF((float)minX,(float)minY),new PointF((float)maxX,(float)minY));
            if (intersetantes.Length >= 1) return true;

            intersetantes = Intersector.Intersection(new PointF((float)ponto.GetX(), (float)ponto.GetY()), new PointF((float)pontoY.GetX(), (float)pontoY.GetY()), new PointF((float)minX, (float)maxY), new PointF((float)maxX, (float)maxY));
            if (intersetantes.Length >= 1) return true;

            intersetantes = Intersector.Intersection(new PointF((float)pontoX.GetX(), (float)pontoX.GetY()), new PointF((float)pontoXY.GetX(), (float)pontoXY.GetY()), new PointF((float)minX, (float)minY), new PointF((float)maxX, (float)minY));
            if (intersetantes.Length >= 1) return true;

            intersetantes = Intersector.Intersection(new PointF((float)pontoX.GetX(), (float)pontoX.GetY()), new PointF((float)pontoXY.GetX(), (float)pontoXY.GetY()), new PointF((float)minX, (float)maxY), new PointF((float)maxX, (float)maxY));
            if (intersetantes.Length >= 1) return true;

            return false;
        }

        private int InserirEncomenda(Planeamento p,Veiculo v,Encomenda e,List<Ponto2D> pontos,Ponto2D pontoFinal)
        {
            Boolean b;
            Ponto2D pontoSucesso=null;

            if (pontos.Count == 0) return VEICULO_CHEIO;
            foreach(Ponto2D ponto in pontos)
            {
                // check medidas do veiculo
                if (ponto.GetX() + e.Largura > v.Largura) continue;
                if (ponto.GetY() + e.Comprimento > v.Comprimento) continue;
                // check entregas, b = ha sobreposicao?
                b = false;
                foreach (Entrega entrega in p.ShallowEntregas)
                {
                    b = CheckSobreposicao(ponto, entrega, e);
                    if (b == true) break;
                }
                if (b == false)
                {
                    pontoSucesso = ponto.Clone();
                    break;
                }
            }
            if (pontoSucesso != null)
            {
                pontos.Remove(pontoSucesso);
                Ponto2D novoPonto;

                novoPonto = pontoSucesso.Clone();
                novoPonto.AddX(e.Largura+0.01);
                // check se ta a menos
                if (novoPonto.GetX() <= v.Largura - 0.1)
                    pontos.Add(novoPonto);

                novoPonto = pontoSucesso.Clone();
                novoPonto.AddY(e.Comprimento+0.01);
                // check se ta a menos
                if (novoPonto.GetY() <= v.Comprimento - 0.1)
                    pontos.Add(novoPonto);

                // retornar ponto final
                pontoFinal.SetX(pontoSucesso.GetX());
                pontoFinal.SetY(pontoSucesso.GetY());
                return SUCESSO;
            }
            return NAO_COUBE;
        }

		private List<Planeamento> Algoritmo(List<Motorista> ms, SortedSet<Veiculo> vs, SortedSet<Encomenda> es)
        {
            List<Planeamento> ps = new List<Planeamento>();
            List<Ponto2D> pontos;
            Planeamento p;
            Entrega entrega;
            Boolean veiculoCheio = false;
            Boolean haEncomendas,b;
            Ponto2D pontoFinal = new Ponto2D();
            Boolean inseridasEncomendas;
            int sucesso;
            int nEncomendas = es.Count,encsProcessadas = 0;
            int nPlaneamentos, plansProcessados = 0;


            processado = 0;
            foreach (Veiculo v in vs)
            {
                haEncomendas = (es.Count > 0) ? true : false;
                if(!haEncomendas) break;

                p = new Planeamento();
                p.ShallowEntregas = new SortedSet<Entrega>(new Entrega.OrdenarPorSaida());
                inseridasEncomendas = false;
                pontos = new List<Ponto2D>();
                pontos.Add(new Ponto2D(0.0, 0.0));

                foreach (Encomenda e in es)
                {
                    if (veiculoCheio)
                    {
                        veiculoCheio = false;
                        break;
                    }
                    
                    sucesso = InserirEncomenda(p,v,e,pontos,pontoFinal);
                    switch (sucesso)
                    {
                        case SUCESSO:
                            e.Processada = DateTime.Now;
                            entrega = new Entrega();
                            entrega.Encomenda = e;
                            entrega.PosX = pontoFinal.GetX();
                            entrega.PosY = pontoFinal.GetY();
                            pontos.Add(new Ponto2D(0.0,pontoFinal.GetY()+e.Comprimento));
                            p.AddEntrega(entrega);
                            inseridasEncomendas = true;
                            encsProcessadas++;
                            processado = (int) (encsProcessadas / nEncomendas * 0.7);
                            newRoutes.setProgress(processado);
                            //System.Threading.Thread.Sleep(500);
                            break;
                        case NAO_COUBE:
                            break;
                        case VEICULO_CHEIO:
                            veiculoCheio = true;
                            break;
                    }
                }
                if (inseridasEncomendas)
                {
                    foreach (Entrega ent in p.ShallowEntregas)
                    {
                        es.Remove(ent.Encomenda);
                    }
                    p.Veiculo = v;
                    p.Data = DateTime.Today;
                    ps.Add(p);
                }
            }

            processado = 70;
            newRoutes.setProgress(processado);

            nPlaneamentos = ps.Count;
            foreach (Planeamento plan in ps)
            {
                b = false;
                foreach (Motorista m in ms)
                {
                    if (m.TiposVeiculos.Contains(plan.Veiculo.Tipo))
                    {
                        b = true;
                        plan.Motorista = m;
                        plansProcessados++;
                        processado = 70 + (int) (plansProcessados / nPlaneamentos * 0.1);
                        newRoutes.setProgress(processado);
                        //System.Threading.Thread.Sleep(500);
                        break;
                    }
                }
                if(b) ms.Remove(plan.Motorista);
            }

            processado = 80;
            newRoutes.setProgress(processado);

            return ps;
		}

	}

}
